
package com.ansion.string;

import android.annotation.SuppressLint;
import android.text.TextUtils;

import androidx.annotation.NonNull;

import java.math.RoundingMode;
import java.net.URI;
import java.text.BreakIterator;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@SuppressLint("DefaultLocale")
public class StringUtils {

    public static boolean isEmpty(String str) {
        return str == null || str.length() == 0;
    }

    public static boolean isNonEmpty(String string) {
        return !isEmpty(string);
    }

    public static boolean isPhoneOnly(@NonNull String str) {
        String content = str.replace(" ", "");
        if (isDigitsOnly(content))
            return true;
        if (content.startsWith("+") && isDigitsOnly(content, 1, content.length())) {
            return true;
        }
        return false;
    }

    public static boolean isDigitsOnly(@NonNull String str, int startIndex, int endIndex) {
        if (startIndex >= endIndex || startIndex >= str.length() || endIndex > str.length()) {
            return false; // return 'false' for empty string
        }
        for (int i = startIndex; i < endIndex; i++) {
            char ch = str.charAt(i);
            if (ch < '0' || ch > '9') {
                return false;
            }
        }
        return true;
    }

    public static boolean isDigitsOnly(@NonNull String str) {
        return isDigitsOnly(str, 0, str.length());
    }

    /***
     * 判断是否纯数字
     * @param str
     * @return
     */
    public static boolean isPhone(String str) {

        //使用正则表达式
        String regex = "[0-9]+";
        return str.matches(regex) && str.length() >= 7 && str.length() <= 20;
    }

    public static String transMapToString(Map<String, Long> map) {
        StringBuilder sb = new StringBuilder();
        for (String key : map.keySet()) {
            Long value = map.get(key);
            String vs = value == null ? "0" : value.toString();
            sb.append(key).append(",").append(vs).append("$");
        }
        if (sb.length() >= 1) {
            sb.deleteCharAt(sb.length() - 1);
        }
        return sb.toString();
    }

    public static Map<String, Long> transStringToMap(String mapString) {
        Map<String, Long> map = new HashMap<String, Long>();

        if (TextUtils.isEmpty(mapString)) {
            return map;
        }
        StringTokenizer items;
        StringTokenizer entries = new StringTokenizer(mapString, "$");

        while (entries.hasMoreTokens()) {
            items = new StringTokenizer(entries.nextToken(), ",");
            try {
                map.put(items.nextToken(), Long.valueOf(items.nextToken()));
            } catch (NumberFormatException ignore) {
            }
        }
        return map;
    }

    public static boolean isEmojiCharacter(int codePoint) {
        return (0x0080 <= codePoint && codePoint <= 0x00A1) ||
                (0x00A9 <= codePoint && codePoint <= 0x00AE) ||
                (0x0300 <= codePoint && codePoint <= 0x03FF) ||
//                (0x0600 <= codePoint && codePoint <= 0x06FF) ||//Arabic symbol.
                (0x0C00 <= codePoint && codePoint <= 0x0C7F) ||
                (0x1DC0 <= codePoint && codePoint <= 0x1DFF) ||
                (0x1E00 <= codePoint && codePoint <= 0x1EFF) ||
                (0x2000 <= codePoint && codePoint <= 0x200F) ||
                (0x203c <= codePoint && codePoint <= 0x2049) ||
                //for 'TM'.
                (0x2122 == codePoint) ||
                (0x2190 <= codePoint && codePoint <= 0x23FF) ||
                (0x2460 <= codePoint && codePoint <= 0x25FF) ||
                (0x2600 <= codePoint && codePoint <= 0x27EF) ||
                (0x2900 <= codePoint && codePoint <= 0x29FF) ||
                (0x2B00 <= codePoint && codePoint <= 0x2BFF) ||
                (0x2C60 <= codePoint && codePoint <= 0x2C7F) ||
                (0x2E00 <= codePoint && codePoint <= 0x2E7F) ||
                (0xA490 <= codePoint && codePoint <= 0xA4CF) ||
                (0xE000 <= codePoint && codePoint <= 0xF8FF) ||
                (0xFE00 <= codePoint && codePoint <= 0xFE0F) ||
                (0xFE30 <= codePoint && codePoint <= 0xFE4F) ||
                (0x1F000 <= codePoint && codePoint <= 0x1F02F) ||
                (0x1F0A0 <= codePoint && codePoint <= 0x1F0FF) ||
                (0x1F100 <= codePoint && codePoint <= 0x1F64F) ||
                (0x1F680 <= codePoint && codePoint <= 0x1F6FF) ||
                (0x1F910 <= codePoint && codePoint <= 0x1F96B) ||
                (0x1F980 <= codePoint && codePoint <= 0x1F9E0);
    }

    /**
     * version:2020-02-26
     *
     * @param source
     * @return
     */
    public static int getCharCount(String source, CharIterator charIterator) {
        BreakIterator breakIterator = BreakIterator.getCharacterInstance();
        breakIterator.setText(source);
        int start = breakIterator.first();
        int count = 0;
        for (int end = breakIterator.next(); end != BreakIterator.DONE; start = end, end = breakIterator.next()) {
            count++;
            if (charIterator != null) {
                String oneChar = source.substring(start, end);
                if (TextUtils.isEmpty(oneChar)) {
                    continue;
                }
                if (!charIterator.getCharString(source.substring(start, end), Character.codePointAt(source, start))) {
                    break;
                }
            }
        }
        return count;
    }

    public static String takeFirstCharacter(String source) {
        BreakIterator breakIterator = BreakIterator.getCharacterInstance();
        breakIterator.setText(source);
        int start = breakIterator.first();
        int count = 0;
        for (int end = breakIterator.next(); end != BreakIterator.DONE; start = end, end = breakIterator.next()) {
            String oneChar = source.substring(start, end);
            if (TextUtils.isEmpty(oneChar)) {
                continue;
            }
            return oneChar;
        }
        return null;
    }

    public static List<String> splitString(String source) {
        BreakIterator breakIterator = BreakIterator.getCharacterInstance();
        breakIterator.setText(source);
        int start = breakIterator.first();
        List<String> strings = new ArrayList<>();
        for (int end = breakIterator.next(); end != BreakIterator.DONE; start = end, end = breakIterator.next()) {
            strings.add(source.substring(start, end));
        }
        return strings;
    }

    /**
     * version:2020-10-12
     *
     * @param source
     * @return
     */
    public static int getEmojiCount(String source) {
        BreakIterator breakIterator = BreakIterator.getCharacterInstance();
        breakIterator.setText(source);
        int start = breakIterator.first();
        int count = 0;
        int codePoint = 0;
        for (int end = breakIterator.next(); end != BreakIterator.DONE; start = end, end = breakIterator.next()) {
            codePoint = Character.codePointAt(source, start);
            if (isEmojiCharacter(codePoint)) {
                count++;
            }
        }
        return count;
    }

    public static int getCharCount(String source) {
        return getCharCount(source, null);
    }

    public static String formatNumber(double number, String formation) {
        DecimalFormat decimalFormat = new DecimalFormat(formation);
        return decimalFormat.format(number);
    }

    public static String formatAmount(double number) {
        NumberFormat numberFormat = NumberFormat.getInstance(Locale.ENGLISH);
        numberFormat.setMaximumFractionDigits(2);
        numberFormat.setMinimumFractionDigits(2);
        numberFormat.setRoundingMode(RoundingMode.HALF_UP);
        numberFormat.setGroupingUsed(true);
        return numberFormat.format(number);
    }

    public interface CharIterator {
        boolean getCharString(String item, int codePoint);
    }

    public static String hideMiddleChar(String content) {
        if (TextUtils.isEmpty(content) || content.length() <= 1) {
            return content;
        }
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append(content.charAt(0));
        for (int i = 0; i < content.length() - 2; i++) {
            stringBuilder.append('*');
        }
        stringBuilder.append(content.charAt(content.length() - 1));
        return stringBuilder.toString();
    }

//    /**
//     * Return the similarity of two string.
//     *
//     * @return The lower the number, the higher the similarity.0 means totally different.
//     */
//    public static int matchStringWithPinyin(String srcString, String descString) {
//        int similarity = matchString(srcString, descString);
//        if (similarity == 0) {
//            similarity = matchString(srcString, Hanzi2Pinyin.getInstance().getAsString(descString));
//            if (similarity > 0) {
//                similarity += 10000;
//            }
//        }
//        return similarity;
//    }

    /**
     * Return the similarity of two string.
     * For ex:input 'ren', this is the orders:
     * 1.renxxxx
     * 2.xxxxren
     * 3.rxxxexxxnxxx
     *
     * @param descString
     * @param srcString
     * @return The lower the number, the higher the similarity.0 means totally different.
     */
    public static int matchString(String srcString, String descString) {
        if (srcString == null) {
            if (descString == null) {
                return 1;
            } else {
                return 0;
            }
        } else {
            if (descString == null) {
                return 0;
            }
        }
        srcString = srcString.toLowerCase();
        descString = descString.toLowerCase();
        int similarity = 0;
        //Check name and input char one by one.
        char[] arrDesc = descString.toCharArray();
        char[] arrSrc = srcString.toCharArray();
        int differentPoint = -1;
        int startPosition = 0;

        for (int srcCharIndex = 0, descCharIndex = 0; srcCharIndex < arrSrc.length; descCharIndex++) {
            //If input length over than name.
            if (descCharIndex >= arrDesc.length) {
                similarity = 0;
                break;
            }
            char descChar = arrDesc[descCharIndex];
            char srcChar = arrSrc[srcCharIndex];
            if (descChar == srcChar) {
                similarity += (descCharIndex + 1);
                srcCharIndex++;
                if (differentPoint < 0) {
                    differentPoint++;
                }
            } else {
                if (differentPoint >= 0) {
                    differentPoint++;
                }
                if (similarity == 0) {
                    startPosition++;
                }
            }
        }
        if (similarity > 0) {
            if (startPosition > 0) {
                similarity *= (startPosition * 10);
            }
            if (differentPoint > 0) {
                similarity *= (differentPoint * 10000);
            }
        }
        return similarity;
    }

    public static String formatStringToSegment3(String value) {
        if (TextUtils.isEmpty(value)) {
            return "";
        }
        if (value.length() <= 3) {
            return value;
        }
        String regex = "(.{3})";
        String result = value.replaceAll(regex, "$1 ");
        return result;
    }

    public static String replaceLineEndings(String value) {
        if (TextUtils.isEmpty(value)) {
            return value;
        }
        String lineSeparator = String.valueOf(((char) 0x2028));
        String paragraphSeparator = String.valueOf(((char) 0x2029));

        value = value.replaceAll(lineSeparator, String.valueOf('\n'));
        value = value.replaceAll(paragraphSeparator, String.valueOf('\n'));

        return value;
    }

    /**
     * Hid string characters in middle position; string.length / 2 chars in middle will be replaced by '*'
     * "abcdefg" --> "ab***fg"
     * "a" --> "*"
     *
     * @param string
     * @return a string by hidden chars in mddile position
     */
    public static String hidMiddleCharacters(String string) {
        if (string == null || TextUtils.isEmpty(string)) {
            return string;
        }
        return hidMiddleCharacters(string, string.length() / 2);
    }

    /**
     * Hid string characters in middle position; 'cnt' chars in middle will be replaced by '*'
     * "abcfefg" --5-> "a*****g"
     * "abcfefg" --10-> "*******"
     *
     * @param string
     * @param cnt    chars want to hid
     * @return a string by hidden chars in mddile position
     */
    public static String hidMiddleCharacters(String string, int cnt) {
        if (string == null || TextUtils.isEmpty(string)) {
            return string;
        }
        if (cnt > string.length()) {
            cnt = string.length();
        }
        if (cnt == 0) {
            return string;
        }

        int startIndex = (string.length() - cnt) / 2;

        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < string.length(); ++i) {
            if (i >= startIndex && i < startIndex + cnt) {
                sb.append('*');
            } else {
                sb.append(string.charAt(i));
            }
        }
        return sb.toString();
    }

    public static List<String> listFromString(String content, String separator) {
        if (TextUtils.isEmpty(content) || TextUtils.isEmpty(separator))
            return null;
        String[] arr = content.split(separator);
        return Arrays.asList(arr);
    }

    public interface SplitPartParser<T> {
        T parse(String part);
    }

    public interface CombineItemStringifier<T> {
        String toString(T item);
    }


    public static String urlRemoveQuery(String url) {
        try {

            URI uri = new URI(url);
            return new URI(uri.getScheme(),
                    uri.getAuthority(),
                    uri.getPath(),
                    null,
                    uri.getFragment()).toString();
        } catch (Exception e) {

            return url.split("\\?")[0];
        }

    }

    public static boolean urlContainsQuery(String url, String queryname) {
        //TODO 需要优化
        return url.contains(queryname);

    }

    public static boolean stringIsEqual(String s1, String s2) {
        if (null == s1) {
            if (null == s2) {
                return true;
            } else {
                return false;
            }
        } else {
            if (null == s2) {
                return false;
            }
            return s1.equals(s2);
        }
    }

    public static boolean stringIsEqualIgnoreNullOrEmpty(String s1, String s2) {
        if (TextUtils.isEmpty(s1) && TextUtils.isEmpty(s2)) {
            return true;
        }
        return stringIsEqual(s1, s2);
    }

    /**
     * 只提取数字
     *
     * @param str
     * @return
     */
    public static String getNumberStr(String str) {
        String regEx = "[^0-9]";
        Pattern p = Pattern.compile(regEx);
        Matcher m = p.matcher(str);
        return m.replaceAll("").trim();


    }


    public static String limitStringLength(String str, int limit) {
        if (str == null)
            return null;
        int len = str.length();
        if (len < 3)
            return str;
        if (len > limit) {
            return str.substring(0, limit - 3) + "...";
        }
        return str;
    }

    public static String limitString(String content, int limit) {
        return (content == null || content.length() <= limit) ? content : content.substring(0, limit);
    }

    static String ChineseRegEx = "[\u4e00-\u9fa5]";
    static Pattern ChinesePat = Pattern.compile(ChineseRegEx);

    public static boolean isContainsChinese(String str) {
        if (TextUtils.isEmpty(str)) {
            return false;
        }
        Matcher matcher = ChinesePat.matcher(str);
        boolean flg = false;
        if (matcher.find()) {
            flg = true;
        }
        return flg;
    }

    static String ArabicRegEx = "[\u0600-\u06ff]";
    static Pattern Arabicpat = Pattern.compile(ArabicRegEx);

    public static boolean isContainsArabic(String str) {
        if (TextUtils.isEmpty(str)) {
            return false;
        }
        Matcher matcher = Arabicpat.matcher(str);
        boolean flg = false;
        if (matcher.find()) {
            flg = true;
        }
        return flg;
    }

    public static boolean isArabChar(char c) {
        return StringUtils.isContainsArabic(c + "");
    }

    public static final int compareString(String a, String b) {
        if (a == b) {
            return 0;
        }

        if (null == a) {
            return (null == b) ? 0 : -1;
        }

        return (null == b) ? 1 : a.compareToIgnoreCase(b);
    }

    public static boolean isAlpha(char c) {

        return ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z');
    }

    public static boolean isDigital(char c) {

        return '0' <= c && c <= '9';
    }

    public static boolean containsIgnoreCase(String value, String key) {
        if (TextUtils.isEmpty(value) || key == null) {
            return false;
        }
        String value2 = value.trim().toLowerCase().replaceAll(" ", "");
        String key2 = key.trim().toLowerCase().replaceAll(" ", "");
        return value2.indexOf(key2) >= 0;
    }

    public final static String getNumberOfString(String s) {
        if (s == null)
            return null;
        StringBuilder sb = new StringBuilder();
        int len = s.length();
        for (int i = 0; i < len; i++) {
            char c = s.charAt(i);
            if (c == '+' || (c >= '0' && c <= '9'))
                sb.append(c);
        }
        return sb.toString();
    }


    public static String padStringAtFirst(String string, int length, char character) {
        StringBuilder sb = new StringBuilder(string);
        while (sb.length() < length) {
            sb.insert(0, character);
        }
        return sb.toString();
    }

    public static String extractLastSixDigits(String number) {
        if (number.length() >= 6) {
            return number.substring(number.length() - 6);
        } else {
            return padStringAtFirst(number, 6, '0');
        }
    }

    public static String extractStringBetween(String source, String start, String end) {
        int startIndex = source.indexOf(start);
        if (startIndex < 0) {
            return null;
        }
        startIndex += start.length();
        int endIndex = source.indexOf(end, startIndex);
        if (endIndex < 0) {
            return null;
        }
        return source.substring(startIndex, endIndex);
    }


    //暂时保留这个方法，为后续rich text搜索清理格式做准备
    public static String cleanRichText(String richText) {
        // 使用正则表达式去除HTML标签
        // 替换所有的HTML标签为一个空字符串
        String noHtml = richText.replaceAll("<[^>]*>", "");

        // 去除多余的空格和换行符
        noHtml = noHtml.replaceAll("\\s+", " ").trim();

        return noHtml;
    }

    public static String generateFTSContent(@NonNull List<String> elements) {
        return generateFTSContent(elements.toArray(new String[elements.size()]));
    }

    public static String generateFTSContent(@NonNull String... elements) {
        if (elements == null) {
            return null;
        }
        if (elements.length == 0) {
            return null;
        }
        if (elements.length == 1) {
            return elements[0];
        }
        StringBuilder stringBuilder = new StringBuilder();
        for (String element : elements) {
            if (element == null) {
                continue;
            }
            String content = element.trim();
            if (TextUtils.isEmpty(content)) {
                continue;
            }
            if (stringBuilder.length() > 0) {
                stringBuilder.append(" ");
            }
            stringBuilder.append(content);
        }
        return stringBuilder.toString();
    }

    public static String extractNumber(String input) {
        if (TextUtils.isEmpty(input)) {
            return "";
        }
        Pattern pattern = Pattern.compile("^\\d+");
        Matcher matcher = pattern.matcher(input.trim());
        if (matcher.find()) {
            return matcher.group();
        } else {
            return null; // or handle the case where no number is found
        }
    }


    public static int matchIndex(String searchContent, String content) {
        if (TextUtils.isEmpty(searchContent)) {
            return 0;
        }
        if (TextUtils.isEmpty(content)) {
            return -1;
        }
        String noSpaceContent = content.toLowerCase().trim().replace(" ", "");
        String noSpaceSearchContent = searchContent.toLowerCase().trim().replace(" ", "");
        return noSpaceContent.indexOf(noSpaceSearchContent);
    }
}
